package com.bt.lms.network;

import com.bt.lms.network.msg.ReqMsg;
import com.bt.lms.network.router.Router;
import com.bt.lms.utils.Convert;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 博添教学管理系统服务器
 *
 * @author 徐大永
 */
public class BotLmsServer {

    /**
     * 启动
     *
     * @param port 端口号
     */
    public void start(int port) {

        try {
            //服务器
            ServerSocket serverSocket = new ServerSocket(port);
            //临时使用一个客户一个线程避免每次重启服务器（后续进行连接数量的限制）
            for (; ; ) {
                //等待客户端
                Socket client = serverSocket.accept();
                //客户端连接后新启一个线程
                Thread thread = new Thread(() -> {
                    try {
                        InputStream in = client.getInputStream();//读流，开读
                        //读流 得全部数据
                        byte[] msgByte = streamRead(in);
                        //流转消息
                        ReqMsg reqMsg = ReqMsg.toReqMsg(msgByte);
                        String magicNo = reqMsg.getMagicNo();
                        if (!magicNo.equals(ReqMsg.NETWORK_MAGIC_NO)) {
                            //魔数错误 直接关闭客户端   后续重新连接即可
                            client.close();
                        } else {
                            Router.route(reqMsg, client);
                        }
                    } catch (IOException e) {
                        throw new RuntimeException(e);//后续替换为自定义异常
                    }
                });
                thread.start();

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }

    /**
     * 字节流转消息包
     * ：读取全部字节，讲读取的字节统一保存成一个字节数组
     * 需要判断数据长度（数据长度做限制，超过一定大小后，走另一套步骤（暂未开发））
     * 后续内容大小做不做限制？
     * 数据长度可能过长，出问题(理论一个几十M没有问题)  做限制
     *
     * @param in 字节输入流
     * @return 消息包（字节数组形式）
     */
    private byte[] streamRead(InputStream in) {

        //要返回的变量
        byte[] sumByte = new byte[0];

        //循环控制变量
        int headLen = ReqMsg.MSG_HEAD_LENGTH;//消息头长度
        int resHeadLen = headLen;//剩余应读量（消息头
        int bodyLen = -1;//消息体长度
        int resBodyLen = bodyLen;//剩余应读量(消息体
        int schedule = 1;//进度  1- 消息头   2-消息体  3-结束
        //消息保留变量（头）
        byte[] mseHeadBytes = new byte[headLen];
        //消息保留变量(体)
        byte[] mseBody = new byte[0];
        //缓冲区
        byte[] buf = new byte[1024];
        //准备开读
        while (true) {
            try {
                //读取量
                int readCon = in.read(buf);

                int resReadConLen; //读的剩余量

                if (readCon == -1) break; //客户端断开，结束循环

                //进度判断 消息头
                if (schedule == 1) {
                    if (readCon < resHeadLen) {
                        //读取数量小于消息头
                        //1.保留
                        int startIndex = headLen - resHeadLen;
                        System.arraycopy(buf, 0, mseHeadBytes, startIndex, readCon);
                        //2.维护变量
                        resHeadLen -= readCon;
                    } else if (readCon == resHeadLen) {
                        //刚好读取到消息头
                        //1.解析消息头 + 维护变量
                        int startIndex = headLen - resHeadLen;
                        System.arraycopy(buf, 0, mseHeadBytes, startIndex, readCon);
                        //解析数据长度
                        byte[] bytes = new byte[4];
                        System.arraycopy(mseHeadBytes, 42, bytes, 0, 4);
                        bodyLen = Convert.bytesToInt(bytes);
                        //2.继续读 同时维护变量
                        mseBody = new byte[bodyLen];
                        resBodyLen = bodyLen;
                        schedule = 2;
                    } else {
                        //读的数量多
                        //1.解析消息头 + 维护变量
                        int startIndex = headLen - resHeadLen;
                        System.arraycopy(buf, 0, mseHeadBytes, startIndex, resHeadLen);
                        //解析数据长度
                        byte[] bytes = new byte[4];
                        System.arraycopy(mseHeadBytes, 42, bytes, 0, 4);
                        bodyLen = Convert.bytesToInt(bytes);
                        //维护变量 读进来的剩余量
                        resReadConLen = readCon - resHeadLen;
                        mseBody = new byte[bodyLen];
                        //2.处理多出来的量
                        //还有剩余量    判断是否包含全部体
                        if (bodyLen == resReadConLen) {  //剩余量与全部体刚刚好
                            //保存数据，控制数据归位 mseBody
                            System.arraycopy(buf, headLen, mseBody, 0, resReadConLen);
                            resHeadLen = headLen;//剩余应读量
                            schedule = 3;//进度更新
                        } else if (bodyLen > resReadConLen) {//剩余量包含一部分全部体 保留数据，重新读取
                            System.arraycopy(buf, headLen, mseBody, 0, resReadConLen);
                            //维护变量
                            resBodyLen -= resReadConLen;//更新剩余应读量
                            schedule = 2;
                        }
                    }
                } else {//进度2，处理消息体 if (schedule == 2)
                    if (readCon < resBodyLen) {//消息体还没有读取完
                        int startIndex = bodyLen - resBodyLen;
                        System.arraycopy(buf, 0, mseBody, startIndex, readCon);
                        //维护变量
                        resBodyLen -= readCon;//更新剩余应读量
                    } else if (readCon == resBodyLen) {//消息体刚好读取完
                        //保存数据，控制数据归位
                        int startIndex = bodyLen - resBodyLen;
                        System.arraycopy(buf, 0, mseBody, startIndex, resBodyLen);
                        schedule = 3;//进度更新
                    } else {
                        int startIndex = bodyLen - resBodyLen;
                        System.arraycopy(buf, 0, mseBody, startIndex, resBodyLen);
                        //维护变量
                        resBodyLen -= readCon;//更新剩余应读量
                        schedule = 3;//进度更新
                    }
                }

                if (schedule == 3) {
                    sumByte = new byte[headLen + bodyLen];
                    System.arraycopy(mseHeadBytes, 0, sumByte, 0, headLen);
                    System.arraycopy(mseBody, 0, sumByte, headLen, bodyLen);
                    break;
                }
            } catch (IOException e) {
                throw new RuntimeException(e);//读取时的异常
            }
        }
        return sumByte;
    }

}
